/*  Pattern based natural language processing tools for Estonian.
    Copyright (C) 2013 University of Tartu

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <Cover.hpp>

#include <boost/unordered_set.hpp>

namespace patnlp
{

StringMapLongVector as_lv_from_oc(std::map<std::string, OrderedDocCover> const& m)
{
    StringMapLongVector _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, i->second.indices()});
    }
    return _m;
}

StringMapLongVector as_lv_from_bc(std::map<std::string, BitsetDocCover> const& m)
{
    StringMapLongVector _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, OrderedDocCover(i->second).indices()});
    }
    return _m;
}

StringMapBoolVector as_bv_from_oc(std::map<std::string, OrderedDocCover> const& m)
{
    StringMapBoolVector _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, BitsetDocCover(i->second).bits()});
    }
    return _m;
}

StringMapBoolVector as_bv_from_bc(std::map<std::string, BitsetDocCover> const& m)
{
    StringMapBoolVector _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, i->second.bits()});
    }
    return _m;
}


StringMapLong as_sizes_from_oc(std::map<std::string, OrderedDocCover> const& m)
{
    StringMapLong _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, i->second.doc_size()});
    }
    return _m;
}

StringMapLong as_sizes_from_bc(std::map<std::string, BitsetDocCover> const& m)
{
    StringMapLong _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, i->second.doc_size()});
    }
    return _m;
}

std::map<std::string, OrderedDocCover> as_oc(StringMapLongVector const& m, StringMapLong const& sz)
throw(std::runtime_error)
{
    std::map<std::string, OrderedDocCover> _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        auto j = sz.find(i->first);
        if (j == sz.end()) {
            throw std::runtime_error(ERR_SIZE_NOT_DEFINED);
        }
        _m.insert(_m.end(), {i->first, OrderedDocCover(j->second, i->second)});
    }
    return _m;
}

std::map<std::string, OrderedDocCover> as_oc(StringMapBoolVector const& m)
{
    std::map<std::string, OrderedDocCover> _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(),
        {i->first, OrderedDocCover(BitsetDocCover(i->second))});
    }
    return _m;
}

std::map<std::string, BitsetDocCover> as_bc(StringMapLongVector const& m, StringMapLong const& sz)
throw (std::runtime_error)
{
    std::map<std::string, BitsetDocCover> _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        auto j = sz.find(i->first);
        if (j == sz.end()) {
            throw std::runtime_error(ERR_SIZE_NOT_DEFINED);
        }
        _m.insert(_m.end(),
        {i->first, BitsetDocCover(OrderedDocCover(j->second, i->second))});
    }
    return _m;
}

std::map<std::string, BitsetDocCover> as_bc(StringMapBoolVector const& m)
{
    std::map<std::string, BitsetDocCover> _m;
    for (auto i=m.begin() ; i!=m.end() ; ++i) {
        _m.insert(_m.end(), {i->first, BitsetDocCover(i->second)});
    }
    return _m;
}

Cover<BitsetDocCover> as_bitset_cover(Cover<OrderedDocCover> const& c)
{
    Cover<BitsetDocCover> bc;
    auto names = c.names();
    for (auto i=names.begin(); i!=names.end() ; ++i) {
        BitsetDocCover bdc(c.doc_cover(*i));
        bc.add_doc_cover(*i, bdc);
    }
    return bc;
}

Cover<OrderedDocCover> as_ordered_cover(Cover<BitsetDocCover> const& c)
{
    Cover<OrderedDocCover> oc;
    auto names = c.names();
    for (auto i=names.begin(); i!=names.end() ; ++i) {
        BitsetDocCover odc(c.doc_cover(*i));
        oc.add_doc_cover(*i, odc);
    }
    return oc;
}

struct CoverSizeSortHelper {
    bool operator()(OrderedCover const&A, OrderedCover const&B) {
        return A.size() > B.size();
    }
};

void sort_by_cover_size(std::vector<OrderedCover>& covers)
{
    sort(covers.begin(), covers.end(), CoverSizeSortHelper());
}

class CumulativeSortHelper {
    std::vector<OrderedCover> const& covers;

public:
    CumulativeSortHelper(std::vector<OrderedCover> const& covers) : covers(covers) {
    }

    bool operator()(long i, long j) {
        return covers[i].size() > covers[j].size();
    }
};

std::vector<long>
cumulative_ordering(std::vector<OrderedCover> const& covers, OrderedCover const& true_cover, size_t const limit)
{
    OrderedCover cumulative;
    OrderedCover tmp;
    std::vector<long> result;
    boost::unordered_set<size_t> used;
    std::vector<long> sorted_idxs;

    // first, create a list if indices of covers sorted by their size
    sorted_idxs.reserve(covers.size());
    for (size_t idx=0 ; idx<covers.size() ; ++idx) {
        sorted_idxs.push_back(idx);
    }
    CumulativeSortHelper helper(covers);
    std::sort(sorted_idxs.begin(), sorted_idxs.end(), helper);

    for (size_t idx=0 ; idx<limit && used.size() <
            covers.size() ; ++idx) {
        size_t best_idx      = 0;
        size_t best_increase = 0;
        for (size_t cov_idx=0 ; cov_idx < covers.size() ; ++cov_idx) {
            // if have already considered the cover, do not waste time
            // testing it again
            auto i = used.find(sorted_idxs[cov_idx]);
            if (i != used.end()) {
                continue;
            }
            // check, if this cover can contribute more than best known so far
            if (covers[sorted_idxs[cov_idx]].size() < best_increase) {
                break; // we are done here.
            }
            // check, how many elements can this cover contribute
            tmp = covers[sorted_idxs[cov_idx]] - cumulative;
            size_t increase = tmp.metrics(true_cover).tp();
            if (increase > best_increase) {
                best_increase = increase;
                best_idx      = sorted_idxs[cov_idx];
            }
        }
        // add the best cover
        used.insert(best_idx);
        cumulative |= covers[best_idx];
        result.push_back(best_idx);

        // are there any elements left
        if (cumulative.metrics(true_cover).tp() ==
                static_cast<long>(true_cover.size())) {
            break;
        }
    }
    return result;
}

} // namespace patnlp
